package mutex

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

/*
   Mutex(互斥锁)
   Mutex 是最简单的一种锁类型，互斥锁，同时也比较暴力，当一个 goroutine 获得了 Mutex 后，
   其他 goroutine 就只能乖乖等到这个 goroutine 释放该 Mutex。

   互斥锁是传统并发编程对共享资源进行访问控制的主要手段，它由标准库sync中的Mutex结构体类型表示。
   sync.Mutex类型只有两个公开的指针方法，Lock和Unlock。Lock锁定当前的共享资源，Unlock进行解锁

   在使用互斥锁时，一定要注意：对资源操作完成后，一定要解锁，否则会出现流程执行异常，死锁等问题。
   通常借助defer。锁定后，立即使用defer语句保证互斥锁及时解锁。


   Lock()方法
   Lock()这个方法，锁定m。如果该锁已在使用中，则调用goroutine将阻塞，直到互斥体可用。

   Unlock()方法
   Unlock()方法，解锁解锁m
*/

//全局变量，表示票
var ticket = 100 //100张票

var mutex sync.Mutex //创建锁头

var wg sync.WaitGroup //同步等待组对象
func main() {

	/*
	   4个goroutine，模拟4个售票口，

	   在使用互斥锁的时候，对资源操作完，一定要解锁。否则会出现程序异常，死锁等问题。
	   defer语句
	*/

	wg.Add(4)
	go saleTickets("售票口01")
	go saleTickets("售票口02")
	go saleTickets("售票口03")
	go saleTickets("售票口04")

	wg.Wait() //main要等待
	fmt.Println("程序结束了。。。")
}

func saleTickets(name string) {
	rand.Seed(time.Now().UnixNano())
	defer wg.Done()
	for {
		//上锁
		mutex.Lock()    //g2
		if ticket > 0 { //ticket 1 g1
			time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
			fmt.Println(name, "售出：", ticket) // 1
			ticket--                         // 0
		} else {
			mutex.Unlock() //条件不满足，也要解锁
			fmt.Println(name, "售罄，没有票了。。")
			break
		}
		mutex.Unlock() //解锁
	}
}
